/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.security;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Base 64 encoding utility
 * <p>
 * <b>Base-64</b><br/> <b>Encoding a String</b><br/> String str=
 * "Welcome to Aphrodite";<br/> String
 * result=Base64.encode(str.getBytes()));<br/> <b>Decoding a String</b><br/>
 * byte[] plain=Base64.decode(result);<br/> String plainText=new String(plain);
 * </p>
 * 
 * @author kentelt
 * 
 */
public class Base64 {

	static final char[] charTab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();

	static int decode(char c) {
		if (c >= 'A' && c <= 'Z')
			return (c) - 65;
		else if (c >= 'a' && c <= 'z')
			return (c) - 97 + 26;
		else if (c >= '0' && c <= '9')
			return (c) - 48 + 26 + 26;
		else
			switch (c) {
			case '+':
				return 62;
			case '/':
				return 63;
			case '=':
				return 0;
			default:
				throw new RuntimeException("unexpected code: " + c);
			}
	}

	/**
	 * Decodes the given Base64 encoded String to a new byte array. The byte
	 * array holding the decoded data is returned.
	 */

	public static byte[] decode(String s) {

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			decode(s, bos);
		} catch (IOException e) {
			throw new RuntimeException();
		}
		return bos.toByteArray();
	}

	public static void decode(String s, OutputStream os) throws IOException {
		int i = 0;

		int len = s.length();

		while (true) {
			while (i < len && s.charAt(i) <= ' ')
				i++;

			if (i == len)
				break;

			int tri = (decode(s.charAt(i)) << 18) + (decode(s.charAt(i + 1)) << 12) + (decode(s.charAt(i + 2)) << 6)
					+ (decode(s.charAt(i + 3)));

			os.write((tri >> 16) & 255);
			if (s.charAt(i + 2) == '=')
				break;
			os.write((tri >> 8) & 255);
			if (s.charAt(i + 3) == '=')
				break;
			os.write(tri & 255);

			i += 4;
		}
	}

	public static String encode(byte[] data) {
		return encode(data, 0, data.length, null).toString();
	}

	/**
	 * Encodes the part of the given byte array denoted by start and len to the
	 * Base64 format. The encoded data is appended to the given StringBuffer. If
	 * no StringBuffer is given, a new one is created automatically. The
	 * StringBuffer is the return value of this method.
	 */
	public static StringBuffer encode(byte[] data, int start, int len, StringBuffer buf) {
		long time = System.currentTimeMillis();
		if (buf == null)
			buf = new StringBuffer(data.length * 3 / 2);

		int end = len - 3;
		int i = start;
		int n = 0;

		while (i <= end) {
			int d = (((data[i]) & 0x0ff) << 16) | (((data[i + 1]) & 0x0ff) << 8) | ((data[i + 2]) & 0x0ff);

			buf.append(charTab[(d >> 18) & 63]);
			buf.append(charTab[(d >> 12) & 63]);
			buf.append(charTab[(d >> 6) & 63]);
			buf.append(charTab[d & 63]);

			i += 3;

			if (n++ >= 14) {
				n = 0;
				buf.append("\r\n");
			}
		}

		if (i == start + len - 2) {
			int d = (((data[i]) & 0x0ff) << 16) | (((data[i + 1]) & 255) << 8);

			buf.append(charTab[(d >> 18) & 63]);
			buf.append(charTab[(d >> 12) & 63]);
			buf.append(charTab[(d >> 6) & 63]);
			buf.append("=");
		} else if (i == start + len - 1) {
			int d = ((data[i]) & 0x0ff) << 16;

			buf.append(charTab[(d >> 18) & 63]);
			buf.append(charTab[(d >> 12) & 63]);
			buf.append("==");
		}
		System.out.println("Time taken by aphrodite: " + (System.currentTimeMillis() - time));
		return buf;
	}
}
